package main

import (
	"C"
	"encoding/json"
	"fmt"
	"newsffi/model"
	"time"

	"gorm.io/driver/mysql"
	"gorm.io/gorm"
	"gorm.io/gorm/logger"
)

// --- Helper Structs for JSON RPC ---
type NewsListParams struct {
	Where map[string]interface{} `json:"where"`
	Order string                 `json:"order"`
	Page  int                    `json:"page"`
	Limit int                    `json:"limit"`
}

type NewsListResult struct {
	Total int64        `json:"total"`
	Data  []model.News `json:"data"`
}

// Config struct to map the yaml structure
type Config struct {
	Database DatabaseConfig `yaml:"database"`
}

type DatabaseConfig struct {
	Host     string `yaml:"host"`
	Port     int    `yaml:"port"`
	User     string `yaml:"user"`
	Password string `yaml:"password"`
	DbName   string `yaml:"dbname"`
	Charset  string `yaml:"charset"`
}

// Global database connection pool
var db *gorm.DB

// --- Initialization Function ---

//export Initialize
func Initialize(host, user, password, dbname, charset *C.char, port C.int) *C.char {
	// Prevent re-initialization
	if db != nil {
		return C.CString(`{"status": "ok", "message": "already initialized"}`)
	}

	goHost := C.GoString(host)
	goUser := C.GoString(user)
	goPassword := C.GoString(password)
	goDbname := C.GoString(dbname)
	goCharset := C.GoString(charset)
	goPort := int(port)

	var err error
	dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=%s&parseTime=True&loc=Local",
		goUser,
		goPassword,
		goHost,
		goPort,
		goDbname,
		goCharset,
	)

	gormDB, err := gorm.Open(mysql.Open(dsn), &gorm.Config{
		Logger: logger.Default.LogMode(logger.Silent), // Use Silent to avoid excessive logging
	})

	if err != nil {
		db = nil // Ensure db is nil on failure
		return C.CString(fmt.Sprintf(`{"error": "failed to connect database: %v"}`, err))
	}

	db = gormDB

	// Auto-migrate schema on first successful initialization
	err = db.AutoMigrate(&model.NewsCategory{}, &model.News{})
	if err != nil {
		db = nil // Rollback on failure
		return C.CString(fmt.Sprintf(`{"error": "database migration failed: %v"}`, err))
	}

	return C.CString(`{"status": "ok"}`)
}

// --- Category Service Functions ---

//export GetCategoryList
func GetCategoryList(whereJson *C.char) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	var where map[string]interface{}
	goWhereJson := C.GoString(whereJson)
	if goWhereJson != "" && goWhereJson != "{}" {
		if err := json.Unmarshal([]byte(goWhereJson), &where); err != nil {
			return C.CString(fmt.Sprintf(`{"error": "invalid where json: %s"}`, err.Error()))
		}
	}

	var categories []model.NewsCategory
	query := db.Model(&model.NewsCategory{})

	if len(where) > 0 {
		query = query.Where(where)
	}

	if err := query.Order("sort asc, id desc").Find(&categories).Error; err != nil {
		return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error()))
	}

	resultJson, err := json.Marshal(categories)
	if err != nil {
		return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error()))
	}

	return C.CString(string(resultJson))
}

//export GetCategoryByID
func GetCategoryByID(id C.int) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	var category model.NewsCategory
	if err := db.First(&category, int(id)).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return C.CString(fmt.Sprintf(`{"error": "category with id %d not found"}`, id))
		}
		return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error()))
	}

	resultJson, err := json.Marshal(category)
	if err != nil {
		return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error()))
	}

	return C.CString(string(resultJson))
}

//export CreateCategory
func CreateCategory(dataJson *C.char) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	var category model.NewsCategory
	goDataJson := C.GoString(dataJson)
	if err := json.Unmarshal([]byte(goDataJson), &category); err != nil {
		return C.CString(fmt.Sprintf(`{"error": "invalid data json: %s"}`, err.Error()))
	}

	category.ID = 0

	if err := db.Create(&category).Error; err != nil {
		return C.CString(fmt.Sprintf(`{"error": "create failed: %s"}`, err.Error()))
	}

	resultJson, err := json.Marshal(category)
	if err != nil {
		return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error()))
	}

	return C.CString(string(resultJson))
}

//export UpdateCategory
func UpdateCategory(id C.int, dataJson *C.char) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	var category model.NewsCategory
	if err := db.First(&category, int(id)).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return C.CString(fmt.Sprintf(`{"error": "category with id %d not found"}`, id))
		}
		return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error()))
	}

	var updateData map[string]interface{}
	goDataJson := C.GoString(dataJson)
	if err := json.Unmarshal([]byte(goDataJson), &updateData); err != nil {
		return C.CString(fmt.Sprintf(`{"error": "invalid data json: %s"}`, err.Error()))
	}

	delete(updateData, "id")
	delete(updateData, "ID")

	if err := db.Model(&category).Updates(updateData).Error; err != nil {
		return C.CString(fmt.Sprintf(`{"error": "update failed: %s"}`, err.Error()))
	}

	resultJson, err := json.Marshal(category)
	if err != nil {
		return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error()))
	}

	return C.CString(string(resultJson))
}

//export DeleteCategory
func DeleteCategory(id C.int) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	result := db.Delete(&model.NewsCategory{}, int(id))
	if result.Error != nil {
		return C.CString(fmt.Sprintf(`{"error": "delete failed: %s"}`, result.Error.Error()))
	}

	if result.RowsAffected == 0 {
		return C.CString(fmt.Sprintf(`{"error": "category with id %d not found or already deleted"}`, id))
	}

	return C.CString(`{"status": "ok"}`)
}

// --- News Service Functions ---

//export GetNewsList
func GetNewsList(paramsJson *C.char) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	var params NewsListParams
	goParamsJson := C.GoString(paramsJson)
	if goParamsJson != "" && goParamsJson != "{}" {
		if err := json.Unmarshal([]byte(goParamsJson), &params); err != nil {
			return C.CString(fmt.Sprintf(`{"error": "invalid params json: %s"}`, err.Error()))
		}
	}

	if params.Page <= 0 {
		params.Page = 1
	}
	if params.Limit <= 0 {
		params.Limit = 20
	}
	if params.Order == "" {
		params.Order = "is_top desc, id desc"
	}

	var news []model.News
	var total int64

	query := db.Model(&model.News{})

	if len(params.Where) > 0 {
		query = query.Where(params.Where)
	}

	if err := query.Count(&total).Error; err != nil {
		return C.CString(fmt.Sprintf(`{"error": "count query failed: %s"}`, err.Error()))
	}

	offset := (params.Page - 1) * params.Limit

	if err := query.Order(params.Order).Offset(offset).Limit(params.Limit).Find(&news).Error; err != nil {
		return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error()))
	}

	result := NewsListResult{
		Total: total,
		Data:  news,
	}

	resultJson, err := json.Marshal(result)
	if err != nil {
		return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error()))
	}

	return C.CString(string(resultJson))
}

//export GetNewsByID
func GetNewsByID(id C.int) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	var news model.News
	if err := db.First(&news, int(id)).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return C.CString(fmt.Sprintf(`{"error": "news with id %d not found"}`, id))
		}
		return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error()))
	}

	resultJson, err := json.Marshal(news)
	if err != nil {
		return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error()))
	}

	return C.CString(string(resultJson))
}

//export CreateNews
func CreateNews(dataJson *C.char) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	var news model.News
	goDataJson := C.GoString(dataJson)
	if err := json.Unmarshal([]byte(goDataJson), &news); err != nil {
		return C.CString(fmt.Sprintf(`{"error": "invalid data json: %s"}`, err.Error()))
	}

	news.ID = 0
	// 自动设置创建时间和更新时间
	now := time.Now()
	news.CreateTime = now
	news.UpdateTime = now

	if err := db.Create(&news).Error; err != nil {
		return C.CString(fmt.Sprintf(`{"error": "create failed: %s"}`, err.Error()))
	}

	resultJson, err := json.Marshal(news)
	if err != nil {
		return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error()))
	}

	return C.CString(string(resultJson))
}

//export UpdateNews
func UpdateNews(id C.int, dataJson *C.char) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	var news model.News
	if err := db.First(&news, int(id)).Error; err != nil {
		if err == gorm.ErrRecordNotFound {
			return C.CString(fmt.Sprintf(`{"error": "news with id %d not found"}`, id))
		}
		return C.CString(fmt.Sprintf(`{"error": "query failed: %s"}`, err.Error()))
	}

	var updateData map[string]interface{}
	goDataJson := C.GoString(dataJson)
	if err := json.Unmarshal([]byte(goDataJson), &updateData); err != nil {
		return C.CString(fmt.Sprintf(`{"error": "invalid data json: %s"}`, err.Error()))
	}

	delete(updateData, "id")
	delete(updateData, "ID")
	
	// 自动设置更新时间
	updateData["update_time"] = time.Now()

	if err := db.Model(&news).Updates(updateData).Error; err != nil {
		return C.CString(fmt.Sprintf(`{"error": "update failed: %s"}`, err.Error()))
	}

	resultJson, err := json.Marshal(news)
	if err != nil {
		return C.CString(fmt.Sprintf(`{"error": "json marshal failed: %s"}`, err.Error()))
	}

	return C.CString(string(resultJson))
}

//export DeleteNews
func DeleteNews(id C.int) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	result := db.Delete(&model.News{}, int(id))
	if result.Error != nil {
		return C.CString(fmt.Sprintf(`{"error": "delete failed: %s"}`, result.Error.Error()))
	}

	if result.RowsAffected == 0 {
		return C.CString(fmt.Sprintf(`{"error": "news with id %d not found or already deleted"}`, id))
	}

	return C.CString(`{"status": "ok"}`)
}

//export IncrementNewsViews
func IncrementNewsViews(id C.int) *C.char {
	if db == nil {
		return C.CString(`{"error": "database connection is not initialized"}`)
	}

	// 使用原生SQL更新views字段，避免并发问题
	result := db.Exec("UPDATE cy_news SET views = views + 1 WHERE id = ? AND delete_time IS NULL", int(id))
	if result.Error != nil {
		return C.CString(fmt.Sprintf(`{"error": "increment views failed: %s"}`, result.Error.Error()))
	}

	if result.RowsAffected == 0 {
		return C.CString(fmt.Sprintf(`{"error": "news with id %d not found"}`, id))
	}

	// 返回更新后的文章信息
	var news model.News
	if err := db.First(&news, int(id)).Error; err != nil {
		// 即使查询失败，views已经更新成功，返回成功状态
		return C.CString(`{"status": "ok", "message": "views incremented successfully"}`)
	}

	resultJson, err := json.Marshal(news)
	if err != nil {
		return C.CString(`{"status": "ok", "message": "views incremented successfully"}`)
	}

	return C.CString(string(resultJson))
}

// main is required for building a C shared library.
func main() {}
